"""\
A pure Python module providing the core RDF constructs.

The modules is intended to provide the core RDF types and interfaces
for working with RDF. The module defines a plugin interface for
parsers, stores, and serializers that other modules can use to
implement parsers, stores, and serializers.

The primary interface `rdf` exposes to work with RDF is
`rdf.Graph`.

A tiny example:

    >>> from rdf import Graph

    >>> g = Graph()
    >>> result = g.parse("http://eikeon.com/foaf.rdf")

    >>> print "graph has %s statements." % len(g)
    graph has 34 statements.
    >>>
    >>> for s, p, o in g:
    ...     if (s, p, o) not in g:
    ...         raise Exception("It better be!")

    >>> s = g.serialize(format='n3')

"""
__docformat__ = "restructuredtext en"

__version__ = "0.1"
__date__ = "not/yet/released"

__all__ = [
    'Term',
    'Identifier',
    'URIRef',
    'BNode',
    'Literal',

    'Namespace',
    'ClosedNamespace',
    'RDF',

    'Graph',
    'Store',
    'Parser',
    
    ]

import os
import abc
import random
import string
import threading
import unicodedata
import urllib.parse
import urllib.request
import xml.sax


class Term(metaclass=abc.ABCMeta):
    """TODO: class level doc."""

    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError

    @abc.abstractmethod
    def __repr__(self):
        raise NotImplementedError

    @abc.abstractmethod
    def __hash__(self):
        raise NotImplementedError

    @abc.abstractmethod
    def __eq__(self, other):
        raise NotImplementedError


class Identifier(Term):
    """TODO: doc."""

    def __init__(self, value):
        """TODO: doc."""
        self.value = value

    def __str__(self):
        """TODO: doc."""
        return self.value

    def __repr__(self):
        """TODO: doc."""
        return "%s.%s('%s')" % (self.__class__.__module__, 
                                self.__class__.__name__, self.value)

    def __hash__(self):
        """TODO: doc."""
        return hash(self.value)

    def __eq__(self, other):
        """TODO: doc."""
        if isinstance(other, self.__class__):
            return self.value.__eq__(other.value)
        else:
            return False


class URIRef(Identifier):
    """TODO: doc."""


bNodeLock = threading.RLock()

def _serial_numbers():
    i = 0
    while 1:
        yield i
        bNodeLock.acquire()
        i = i + 1
        bNodeLock.release()

_serial_number_generator = _serial_numbers()


class BNode(Identifier):
    """TODO: doc."""

    _prefix="".join((random.choice(string.ascii_letters) for i in range(0, 8)))

    def __init__(self, value=None):
        if value is None:
            node_id = next(_serial_number_generator)
            value = "%s%s" % (BNode._prefix, node_id)
        Identifier.__init__(self, value)


class Literal(Term):
    """TODO: doc."""

    def __init__(self, value, language=None, datatype=None):
        """TODO: doc."""
        self.value = value
        self.language = language
        self.datatype = datatype

    def __str__(self):
        """TODO: doc."""
        return self.value

    def __repr__(self):
        """TODO: doc."""
        args = [repr(self.value)]
        if self.language is not None:
            args.append("language=%s" % repr(self.language))
        if self.datatype is not None:
            args.append("datatype=%s" % repr(self.datatype))
        return """rdf.Literal(%s)""" % ", ".join(args)

    def __hash__(self):
        """TODO: doc."""
        return hash(self.value) ^ hash(self.language) ^ hash(self.datatype) 

    def __eq__(self, other):
        """TODO: doc."""
        if isinstance(other, Literal):
            return self.value.__eq__(other.value) and \
                self.language.__eq__(other.language) and \
                self.datatype.__eq__(other.datatype)

        else:
            return False


class Namespace(object):
    """
    
    """

    def __init__(self, uri):
        self._uri = str(uri)

    def _term(self, name):
        return URIRef("%s%s" % (self._uri, name))

    def __getitem__(self, key, default=None):
        return self._term(key)

    def __getattr__(self, name):
        #if name.startswith("_"): # TODO: ?
        if name.startswith("__"): # ignore any special Python names!
            raise AttributeError
        else:
            return self._term(name)

    def __str__(self):
        return self._uri

    def __repr__(self):
        return """rdf.Namespace('%s')""" % self._uri


class ClosedNamespace(Namespace):
    """
    """

    def __init__(self, uri, terms):
        Namespace.__init__(self, uri)
        self.__uris = {}
        for t in terms:
            self.__uris[t] = URIRef("%s%s" % (self._uri, t))

    def _term(self, name):
        uri = self.__uris.get(name)
        if uri is None:
            raise Exception("term '%s' not in namespace '%s'" % (name, self._uri))
        else:
            return uri


RDF = ClosedNamespace(
    uri=URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#"), 
    terms=[
        # Syntax Names
        "RDF", "Description", "ID", "about", "parseType", "resource", "li", 
        "nodeID", "datatype", 
        
        # RDF Classes
        "Seq", "Bag", "Alt", "Statement", "Property", "XMLLiteral", "List", 
        
        # RDF Properties
        "subject", "predicate", "object", "type", "value", "first", "rest", 
        # and _n where n is a non-negative integer
        
        # RDF Resources          
        "nil"]
            )


class Store(metaclass=abc.ABCMeta):
    pass


class Graph(object):

    def __init__(self, identifier=None, store=None): 
        self.__identifier = identifier or BNode()
        if store is None:
            self.__store = IOMemory()
        elif isinstance(store, Store):
            self.__store = store
        else:
            raise TypeError("store must be an instance of rdf.Store")
        
    def parse(self, source=None):
        p = RDFXMLParser()
        source = URLInputSource(source)
        p.parse(source, self)
        return self

    def add(self, triple):
        """Add a triple with self as context"""
        self.__store.add(triple, self, quoted=False)

    def bind(self, prefix, namespace, override=True):
        """Bind prefix to namespace

        If override is True will bind namespace to given prefix if namespace
        was already bound to a different prefix.
        """
        #return self.namespace_manager.bind(prefix, namespace, override=override)
        return self.__store.bind(prefix, namespace, override)

    def triples(self, triple):
        """Generator over the triple store

        Returns triples that match the given triple pattern. If triple pattern
        does not provide a context, all contexts will be searched.
        """
        for triple, cg in self.__store.triples(triple, context=self):
            yield triple

    def __iter__(self):
        """Iterates over all triples in the store"""
        return self.triples((None, None, None))

    def __len__(self):
        """Returns the number of triples in the graph

        If context is specified then the number of triples in the context is
        returned instead.
        """
        return self.__store.__len__(context=self)


class ParserError(Exception):
    pass


class Parser(metaclass=abc.ABCMeta):
    def __init__(self):
        pass

    def parse(self, source, sink):
        pass


headers = {
    'User-agent': 'rdf-%s (http://code.google.com/p/pyrdf/; eikeon@eikeon.com)' % __version__
    }


class URLInputSource(xml.sax.xmlreader.InputSource):
    """
    TODO:
    """

    def __init__(self, system_id=None, format=None):
        super(URLInputSource, self).__init__(str(system_id))
        self.url = system_id

        # copy headers to change
        myheaders=dict(headers)
        if format=='application/rdf+xml':                 
            myheaders['Accept']='application/rdf+xml, */*;q=0.1'
        elif format=='n3':
            myheaders['Accept']='text/n3, */*;q=0.1'
        elif format=='nt':
            myheaders['Accept']='text/plain, */*;q=0.1'
        else: 
            myheaders['Accept']='application/rdf+xml,text/rdf+n3;q=0.9,application/xhtml+xml;q=0.5, */*;q=0.1'
        
        req = urllib.request.Request(system_id, None, myheaders)
        try:
            file = urllib.request.urlopen(req)
        except urllib.request.HTTPError as e:
            # TODO:
            raise Exception('"%s" while trying to open "%s"' % (e, self.url))
        self.content_type = file.info().get('content-type')
        self.content_type = self.content_type.split(";", 1)[0]
        self.setByteStream(file)
        # TODO: self.setEncoding(encoding)

    def __repr__(self):
        return self.url


# http://www.w3.org/TR/rdf-syntax-grammar/#eventterm-attribute-URI
# A mapping from unqualified terms to there qualified version.
UNQUALIFIED = {
    "about" : RDF.about,
    "ID" : RDF.ID,
    "type" : RDF.type,
    "resource": RDF.resource,
    "parseType": RDF.parseType
    }

# http://www.w3.org/TR/rdf-syntax-grammar/#coreSyntaxTerms
CORE_SYNTAX_TERMS = (RDF.RDF, RDF.ID, RDF.about, RDF.parseType, RDF.resource, RDF.nodeID, RDF.datatype)

# http://www.w3.org/TR/rdf-syntax-grammar/#syntaxTerms
SYNTAX_TERMS = CORE_SYNTAX_TERMS + (RDF.Description, RDF.li)

# http://www.w3.org/TR/rdf-syntax-grammar/#oldTerms
OLD_TERMS = (
    URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#aboutEach"), 
    URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#aboutEachPrefix"), 
    URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#bagID")
    )

NODE_ELEMENT_EXCEPTIONS = CORE_SYNTAX_TERMS + (RDF.li,) + OLD_TERMS
NODE_ELEMENT_ATTRIBUTES = (RDF.ID, RDF.nodeID, RDF.about)

PROPERTY_ELEMENT_EXCEPTIONS = CORE_SYNTAX_TERMS + (RDF.Description,) + OLD_TERMS
PROPERTY_ATTRIBUTE_EXCEPTIONS = CORE_SYNTAX_TERMS + (RDF.Description, RDF.li) + OLD_TERMS
PROPERTY_ELEMENT_ATTRIBUTES = (RDF.ID, RDF.resource, RDF.nodeID)

XMLNS = "http://www.w3.org/XML/1998/namespace"
BASE = (XMLNS, "base")
LANG = (XMLNS, "lang")


class BagID(URIRef):
    __slots__ = ['li']
    def __init__(self, val):
        super(URIRef, self).__init__(val)
        self.li = 0

    def next_li(self):
        self.li += 1
        return RDF[self.li]


class ElementHandler(object):
    __slots__ = ['start', 'char', 'end', 'li', 'id',
                 'base', 'subject', 'predicate', 'object',
                 'list', 'language', 'datatype', 'declared', 'data']
    def __init__(self):
        self.start = None
        self.char = None
        self.end = None
        self.li = 0
        self.id = None
        self.base = None
        self.subject = None
        self.object = None
        self.list = None
        self.language = None
        self.datatype = None
        self.declared = None
        self.data = None

    def next_li(self):
        self.li += 1
        return RDF[self.li]


class RDFXMLHandler(xml.sax.handler.ContentHandler):

    def __init__(self, store):
        self.store = store
        self.preserve_bnode_ids = False
        self.reset()

    def reset(self):
        document_element = ElementHandler()
        document_element.start = self.document_element_start
        document_element.end = lambda name, qname: None
        self.stack = [None, document_element,]
        self.ids = {} # remember IDs we have already seen
        self.bnode = {}
        self._ns_contexts = [{}] # contains uri -> prefix dicts
        self._current_context = self._ns_contexts[-1]

    # ContentHandler methods

    def setDocumentLocator(self, locator):
        self.locator = locator

    def startDocument(self):
        pass

    def startPrefixMapping(self, prefix, namespace):
        self._ns_contexts.append(self._current_context.copy())
        self._current_context[namespace] = prefix
        self.store.bind(prefix, URIRef(namespace), override=False)

    def endPrefixMapping(self, prefix):
        self._current_context = self._ns_contexts[-1]
        del self._ns_contexts[-1]

    def startElementNS(self, name, qname, attrs):
        stack = self.stack
        stack.append(ElementHandler())
        current = self.current
        parent = self.parent
        base = attrs.get(BASE, None)
        if base is not None:
            base, frag = urllib.parse.urldefrag(base)
            if parent and parent.base:
                base = urllib.parse.urljoin(parent.base, base)
            else:
                systemId = self.locator.getPublicId() or self.locator.getSystemId()
                if systemId:
                    base = urllib.parse.urljoin(systemId, base)
        else:
            if parent:
                base = parent.base
            if base is None:
                systemId = self.locator.getPublicId() or self.locator.getSystemId()
                if systemId:
                    base, frag = urllib.parse.urldefrag(systemId)
        current.base = base
        language = attrs.get(LANG, None)
        if language is None:
            if parent:
                language = parent.language
        current.language = language
        current.start(name, qname, attrs)

    def endElementNS(self, name, qname):
        self.current.end(name, qname)
        self.stack.pop()

    def characters(self, content):
        char = self.current.char
        if char:
            char(content)

    def ignorableWhitespace(self, content):
        pass

    def processingInstruction(self, target, data):
        pass

    def add_reified(self, sid, triple):
        (s, p, o) = triple
        self.store.add((sid, RDF.type, RDF.Statement))
        self.store.add((sid, RDF.subject, s))
        self.store.add((sid, RDF.predicate, p))
        self.store.add((sid, RDF.object, o))

    def error(self, message):
        locator = self.locator
        info = "%s:%s:%s: " % (locator.getSystemId(),
                            locator.getLineNumber(), locator.getColumnNumber())
        raise ParserError(info + message)

    def get_current(self):
        return self.stack[-2]
    # Create a read only property called current so that self.current
    # give the current element handler.
    current = property(get_current)

    def get_next(self):
        return self.stack[-1]
    # Create a read only property that gives the element handler to be
    # used for the next element.
    next = property(get_next)

    def get_parent(self):
        return self.stack[-3]
    # Create a read only property that gives the current parent
    # element handler
    parent = property(get_parent)

    def absolutize(self, uri):
        uri = str(uri)
        result = urllib.parse.urljoin(self.current.base, uri, allow_fragments=1)
        if uri and uri[-1]=="#" and result[-1]!="#":
            result = "%s#" % result
        return URIRef(result)

    def convert(self, name, qname, attrs):
        if name[0] is None:
            name = URIRef(name[1])
        else:
            name = URIRef("".join(name))
        atts = {}
        for (n, v) in attrs.items(): 
            if n[0] is None:
                att = n[1]
            else:
                att = "".join(n)
            if att.startswith(XMLNS) or att[0:3].lower()=="xml":
                pass
            elif att in UNQUALIFIED:
                atts[RDF[att]] = v
            else:
                atts[URIRef(att)] = v
        return name, atts

    def document_element_start(self, name, qname, attrs):
        if name[0] and URIRef("".join(name)) == RDF.RDF:
            next = self.next
            next.start = self.node_element_start
            next.end = self.node_element_end
        else:
            self.node_element_start(name, qname, attrs)

    def node_element_start(self, name, qname, attrs):
        name, atts = self.convert(name, qname, attrs)
        current = self.current
        absolutize = self.absolutize
        next = self.next
        next.start = self.property_element_start
        next.end = self.property_element_end

        if name in NODE_ELEMENT_EXCEPTIONS:
            self.error("Invalid node element URI: %s" % name)

        if RDF.ID in atts:
            if RDF.about in atts or RDF.nodeID in atts:
                self.error("Can have at most one of rdf:ID, rdf:about, and rdf:nodeID")

            id = atts[RDF.ID]
            if not is_ncname(id):
                self.error("rdf:ID value is not a valid NCName: %s" % id)
            subject = absolutize("#%s" % id)
            if subject in self.ids:
                self.error("two elements cannot use the same ID: '%s'" % subject)
            self.ids[subject] = 1 # IDs can only appear once within a document
        elif RDF.nodeID in atts:
            if RDF.ID in atts or RDF.about in atts:
                self.error("Can have at most one of rdf:ID, rdf:about, and rdf:nodeID")
            nodeID = atts[RDF.nodeID]
            if not is_ncname(nodeID):
                self.error("rdf:nodeID value is not a valid NCName: %s" % nodeID)
            if self.preserve_bnode_ids is False:
                if nodeID in self.bnode:
                    subject = self.bnode[nodeID]
                else:
                    subject = BNode()
                    self.bnode[nodeID] = subject
            else:
                subject = BNode(nodeID)
        elif RDF.about in atts:
            if RDF.ID in atts or RDF.nodeID in atts:
                self.error("Can have at most one of rdf:ID, rdf:about, and rdf:nodeID")
            subject = absolutize(atts[RDF.about])
        else:
            subject = BNode()

        if name!=RDF.Description: # S1
            self.store.add((subject, RDF.type, absolutize(name)))

        language = current.language
        for att in atts:
            if not str(att).startswith(str(RDF)): # TODO: can this turn into a meaningful method on URIRef
                predicate = absolutize(att)
                try:
                    object = Literal(atts[att], language)
                except Error as e:
                    self.error(e.msg)
            elif att==RDF.type: #S2
                predicate = RDF.type
                object = absolutize(atts[RDF.type])
            elif att in NODE_ELEMENT_ATTRIBUTES:
                continue
            elif att in PROPERTY_ATTRIBUTE_EXCEPTIONS: #S3
                self.error("Invalid property attribute URI: %s" % att)
                continue # for when error does not throw an exception
            else:
                predicate = absolutize(att)
                try:
                    object = Literal(atts[att], language)
                except Error as e:
                    self.error(e.msg)
            self.store.add((subject, predicate, object))

        current.subject = subject


    def node_element_end(self, name, qname):
        self.parent.object = self.current.subject

    def property_element_start(self, name, qname, attrs):
        name, atts = self.convert(name, qname, attrs)
        current = self.current
        absolutize = self.absolutize
        next = self.next
        object = None
        current.data = None
        current.list = None

        if not str(name).startswith(str(RDF)): # TODO
            current.predicate = absolutize(name)
        elif name==RDF.li:
            current.predicate = current.next_li()
        elif name in PROPERTY_ELEMENT_EXCEPTIONS:
            self.error("Invalid property element URI: %s" % name)
        else:
            current.predicate = absolutize(name)

        id = atts.get(RDF.ID, None)
        if id is not None:
            if not is_ncname(id):
                self.error("rdf:ID value is not a value NCName: %s" % id)
            current.id = absolutize("#%s" % id)
        else:
            current.id = None

        resource = atts.get(RDF.resource, None)
        nodeID = atts.get(RDF.nodeID, None)
        parse_type = atts.get(RDF.parseType, None)
        if resource is not None and nodeID is not None:
            self.error("Property element cannot have both rdf:nodeID and rdf:resource")
        if resource is not None:
            object = absolutize(resource)
            next.start = self.node_element_start
            next.end = self.node_element_end
        elif nodeID is not None:
            if not is_ncname(nodeID):
                self.error("rdf:nodeID value is not a valid NCName: %s" % nodeID)
            if self.preserve_bnode_ids is False:
                if nodeID in self.bnode:
                    object = self.bnode[nodeID]
                else:
                    subject = BNode()
                    self.bnode[nodeID] = subject
                    object = subject
            else:
                object = subject = BNode(nodeID)
            next.start = self.node_element_start
            next.end = self.node_element_end
        else:
            if parse_type is not None:
                for att in atts:
                    if att!=RDF.parseType and att!=RDF.ID:
                        self.error("Property attr '%s' now allowed here" % att)
                if parse_type=="Resource":
                    current.subject = object = BNode()
                    current.char = self.property_element_char
                    next.start = self.property_element_start
                    next.end = self.property_element_end
                elif parse_type=="Collection":
                    current.char = None
                    object = current.list = RDF.nil #BNode()#self.parent.subject
                    next.start = self.node_element_start
                    next.end = self.list_node_element_end
                else: #if parse_type=="Literal":
                     # All other values are treated as Literal
                     # See: http://www.w3.org/TR/rdf-syntax-grammar/#parseTypeOtherPropertyElt
                    object = Literal("", datatype=RDF.XMLLiteral)
                    current.char = self.literal_element_char
                    current.declared = {}
                    next.start = self.literal_element_start
                    next.char = self.literal_element_char
                    next.end = self.literal_element_end
                current.object = object
                return
            else:
                object = None
                current.char = self.property_element_char
                next.start = self.node_element_start
                next.end = self.node_element_end

        datatype = current.datatype = atts.get(RDF.datatype, None)
        language = current.language
        if datatype is not None:
            # TODO: check that there are no atts other than datatype and id
            datatype = absolutize(datatype)
        else:
            for att in atts:
                if not str(att).startswith(str(RDF)):
                    predicate = absolutize(att)
                elif att in PROPERTY_ELEMENT_ATTRIBUTES:
                    continue
                elif att in PROPERTY_ATTRIBUTE_EXCEPTIONS:
                    self.error("""Invalid property attribute URI: %s""" % att)
                else:
                    predicate = absolutize(att)

                if att==RDF.type:
                    o = URIRef(atts[att])
                else:
                    if datatype is not None:
                        language = None
                    o = Literal(atts[att], language, datatype)

                if object is None:
                    object = BNode()
                self.store.add((object, predicate, o))
        if object is None:
            current.data = ""
            current.object = None
        else:
            current.data = None
            current.object = object

    def property_element_char(self, data):
        current = self.current
        if current.data is not None:
            current.data += data

    def property_element_end(self, name, qname):
        current = self.current
        if current.data is not None and current.object is None:
            literalLang = current.language
            if current.datatype is not None:
                literalLang = None
            current.object = Literal(current.data, literalLang, current.datatype)
            current.data = None
        if self.next.end==self.list_node_element_end:
            if current.object!=RDF.nil:
                self.store.add((current.list, RDF.rest, RDF.nil))
        if current.object is not None:
            self.store.add((self.parent.subject, current.predicate, current.object))
            if current.id is not None:
                self.add_reified(current.id, (self.parent.subject,
                                 current.predicate, current.object))
        current.subject = None

    def list_node_element_end(self, name, qname):
        current = self.current
        if self.parent.list==RDF.nil:
            list = BNode()
            # Removed between 20030123 and 20030905
            #self.store.add((list, RDF.type, LIST))
            self.parent.list = list
            self.store.add((self.parent.list, RDF.first, current.subject))
            self.parent.object = list
            self.parent.char = None
        else:
            list = BNode()
            # Removed between 20030123 and 20030905
            #self.store.add((list, RDF.type, LIST))
            self.store.add((self.parent.list, RDF.rest, list))
            self.store.add((list, RDF.first, current.subject))
            self.parent.list = list

    def literal_element_start(self, name, qname, attrs):
        current = self.current
        self.next.start = self.literal_element_start
        self.next.char = self.literal_element_char
        self.next.end = self.literal_element_end
        current.declared = self.parent.declared.copy()
        if name[0]:
            prefix = self._current_context[name[0]]
            if prefix:
                current.object = "<%s:%s" % (prefix, name[1])
            else:
                current.object = "<%s" % name[1]
            if not name[0] in current.declared:
                current.declared[name[0]] = prefix
                if prefix:
                    current.object += (' xmlns:%s="%s"' % (prefix, name[0]))
                else:
                    current.object += (' xmlns="%s"' % name[0])
        else:
            current.object = "<%s" % name[1]

        for (name, value) in attrs.items():
            if name[0]:
                if not name[0] in current.declared:
                    current.declared[name[0]] = self._current_context[name[0]]
                name = current.declared[name[0]] + ":" + name[1]
            else:
                name = name[1]
            current.object += (' %s=%s' % (name, 
                                           xml.sax.saxutils.quoteattr(value)))
        current.object += ">"

    def literal_element_char(self, data):
        self.current.object += xml.sax.saxutils.escape(data)

    def literal_element_end(self, name, qname):
        if name[0]:
            prefix = self._current_context[name[0]]
            if prefix:
                end = "</%s:%s>" % (prefix, name[1])
            else:
                end = "</%s>" % name[1]
        else:
            end = "</%s>" % name[1]
        self.parent.object += self.current.object + end


class RDFXMLParser(Parser):

    def __init__(self):
        pass

    def parse(self, source, graph, **args):
        parser = xml.sax.make_parser()
        try:
            # Workaround for bug in expatreader.py. Needed when
            # expatreader is trying to guess a prefix.
            parser.start_namespace_decl("xml", "http://www.w3.org/XML/1998/namespace")
        except AttributeError:
            pass # Not present in Jython (at least)
        parser.setFeature(xml.sax.handler.feature_namespaces, 1)
        rdfxml = RDFXMLHandler(graph)
        rdfxml.setDocumentLocator(source)
        #rdfxml.setDocumentLocator(_Locator(self.url, self.parser))
        parser.setContentHandler(rdfxml)
        parser.setErrorHandler(xml.sax.ErrorHandler())

        content_handler = parser.getContentHandler()
        preserve_bnode_ids = args.get("preserve_bnode_ids", None)
        if preserve_bnode_ids is not None:
            content_handler.preserve_bnode_ids = preserve_bnode_ids
        # We're only using it once now
        #content_handler.reset()
        #self._parser.reset()
        parser.parse(source)


ANY = None


class IOMemory(Store):
    """\
    An integer-key-optimized-context-aware-in-memory store.

    Uses nested dictionaries to store triples and context. Each triple
    is stored in six such indices as follows cspo[c][s][p][o] = 1
    and cpos[c][p][o][s] = 1 and cosp[c][o][s][p] = 1 as well as
    spo[s][p][o] = [c] and pos[p][o][s] = [c] and pos[o][s][p] = [c]

    Context information is used to track the 'source' of the triple
    data for merging, unmerging, remerging purposes.  context aware
    store stores consume more memory size than non context stores.

    """

    context_aware = True
    formula_aware = True

    def __init__(self, configuration=None, identifier=None):
        super(IOMemory, self).__init__()

        # indexed by [context][subject][predicate][object] = 1
        self.cspo = self.createIndex()

        # indexed by [context][predicate][object][subject] = 1
        self.cpos = self.createIndex()

        # indexed by [context][object][subject][predicate] = 1
        self.cosp = self.createIndex()

        # indexed by [subject][predicate][object] = [context]
        self.spo = self.createIndex()

        # indexed by [predicate][object][subject] = [context]
        self.pos = self.createIndex()

        # indexed by [object][subject][predicate] = [context]
        self.osp = self.createIndex()

        # indexes integer keys to identifiers
        self.forward = self.createForward()

        # reverse index of forward
        self.reverse = self.createReverse()

        self.identifier = identifier or BNode()

        self.__namespace = self.createPrefixMap()
        self.__prefix = self.createPrefixMap()

    def bind(self, prefix, namespace, override=None):
        # TODO: override
        # TODO: Sink interface for parser
        self.__prefix[namespace] = prefix
        self.__namespace[prefix] = namespace

    def namespace(self, prefix):
        return self.__namespace.get(prefix, None)

    def prefix(self, namespace):
        return self.__prefix.get(namespace, None)

    def namespaces(self):
        for prefix, namespace in self.__namespace.iteritems():
            yield prefix, namespace

    def intToIdentifier(self, triple):
        si, pi, oi = triple
        """ Resolve an integer triple into identifers. """
        return (self.forward[si], self.forward[pi], self.forward[oi])

    def identifierToInt(self, triple):
        s, p, o = triple
        """ Resolve an identifier triple into integers. """
        return (self.reverse[s], self.reverse[p], self.reverse[o])

    def createForward(self):
        return {}

    def createReverse(self):
        return {}

    def createIndex(self):
        return {}

    def createPrefixMap(self):
        return {}

    def add(self, triple, context, quoted=False):
        """\
        Add a triple to the store.
        """
        #Store.add(self, triple, context, quoted)
        for triple, cg in self.triples(triple, context):
            #triple is already in the store.
            return

        subject, predicate, object = triple

        f = self.forward
        r = self.reverse

        # assign keys for new identifiers

        if not subject in r:
            si=randid()
            while si in f:
                si=randid()
            f[si] = subject
            r[subject] = si
        else:
            si = r[subject]

        if not predicate in r:
            pi=randid()
            while pi in f:
                pi=randid()
            f[pi] = predicate
            r[predicate] = pi
        else:
            pi = r[predicate]

        if not object in r:
            oi=randid()
            while oi in f:
                oi=randid()
            f[oi] = object
            r[object] = oi
        else:
            oi = r[object]

        if not context in r:
            ci=randid()
            while ci in f:
                ci=randid()
            f[ci] = context
            r[context] = ci
        else:
            ci = r[context]

        # add dictionary entries for cspo[c][s][p][o] = 1,
        # cpos[c][p][o][s] = 1, and cosp[c][o][s][p] = 1, creating the
        # nested {} where they do not yet exits.
        self._setNestedIndex(self.cspo, ci, si, pi, oi)
        self._setNestedIndex(self.cpos, ci, pi, oi, si)
        self._setNestedIndex(self.cosp, ci, oi, si, pi)

        if not quoted:
            self._setNestedIndex(self.spo, si, pi, oi, ci)
            self._setNestedIndex(self.pos, pi, oi, si, ci)
            self._setNestedIndex(self.osp, oi, si, pi, ci)

    def _setNestedIndex(self, index, *keys):
        for key in keys[:-1]:
            if not key in index:
                index[key] = self.createIndex()
            index = index[key]
        index[keys[-1]] = 1

    def _removeNestedIndex(self, index, *keys):
        """ Remove context from the list of contexts in a nested index.

        Afterwards, recursively remove nested indexes when they became empty.
        """
        parents = []
        for key in keys[:-1]:
            parents.append(index)
            index = index[key]
        del index[keys[-1]]

        n = len(parents)
        for i in range(n):
            index = parents[n-1-i]
            key = keys[n-1-i]
            if len(index[key]) == 0:
                del index[key]

    def remove(self, triple, context=None):
        Store.remove(self, triple, context)
        if context is not None:
            if context == self:
                context = None

        f = self.forward
        r = self.reverse
        if context is None:
            for triple, cg in self.triples(triple):
                subject, predicate, object = triple
                si, pi, oi = self.identifierToInt((subject, predicate, object))
                contexts = list(self.contexts(triple))
                for context in contexts:
                    ci = r[context]
                    del self.cspo[ci][si][pi][oi]
                    del self.cpos[ci][pi][oi][si]
                    del self.cosp[ci][oi][si][pi]

                    self._removeNestedIndex(self.spo, si, pi, oi, ci)
                    self._removeNestedIndex(self.pos, pi, oi, si, ci)
                    self._removeNestedIndex(self.osp, oi, si, pi, ci)
                    # grr!! hafta ref-count these before you can collect them dumbass!
                    #del f[si], f[pi], f[oi]
                    #del r[subject], r[predicate], r[object]
        else:
            subject, predicate, object = triple
            ci = r.get(context, None)
            if ci:
                for triple, cg in self.triples(triple, context):
                    si, pi, oi = self.identifierToInt(triple)
                    del self.cspo[ci][si][pi][oi]
                    del self.cpos[ci][pi][oi][si]
                    del self.cosp[ci][oi][si][pi]

                    try:
                        self._removeNestedIndex(self.spo, si, pi, oi, ci)
                        self._removeNestedIndex(self.pos, pi, oi, si, ci)
                        self._removeNestedIndex(self.osp, oi, si, pi, ci)
                    except KeyError:
                        # the context may be a quoted one in which
                        # there will not be a triple in spo, pos or
                        # osp. So ignore any KeyErrors
                        pass
                    # TODO delete references to resources in self.forward/self.reverse
                    # that are not in use anymore...

            if subject is None and predicate is None and object is None:
                # remove context
                try:
                    ci = self.reverse[context]
                    del self.cspo[ci], self.cpos[ci], self.cosp[ci]
                except KeyError:
                    # TODO: no exception when removing non-existant context?
                    pass


    def triples(self, triple, context=None):
        """A generator over all the triples matching """

        if context is not None:
            if context == self:
                context = None

        subject, predicate, object = triple
        ci = si = pi = oi = ANY

        if context is None:
            spo = self.spo
            pos = self.pos
            osp = self.osp
        else:
            try:
                ci = self.reverse[context]  # TODO: Really ignore keyerror here
                spo = self.cspo[ci]
                pos = self.cpos[ci]
                osp = self.cosp[ci]
            except KeyError:
                return
        try:
            if subject is not ANY:
                si = self.reverse[subject] # throws keyerror if subject doesn't exist ;(
            if predicate is not ANY:
                pi = self.reverse[predicate]
            if object is not ANY:
                oi = self.reverse[object]
        except KeyError as e:
            return #raise StopIteration

        if si != ANY: # subject is given
            if si in spo:
                subjectDictionary = spo[si]
                if pi != ANY: # subject+predicate is given
                    if pi in subjectDictionary:
                        if oi!= ANY: # subject+predicate+object is given
                            if oi in subjectDictionary[pi]:
                                ss, pp, oo = self.intToIdentifier((si, pi, oi))
                                yield (ss, pp, oo), (c for c in self.contexts((ss, pp, oo)))
                            else: # given object not found
                                pass
                        else: # subject+predicate is given, object unbound
                            for o in subjectDictionary[pi].keys():
                                ss, pp, oo = self.intToIdentifier((si, pi, o))
                                yield (ss, pp, oo), (c for c in self.contexts((ss, pp, oo)))
                    else: # given predicate not found
                        pass
                else: # subject given, predicate unbound
                    for p in subjectDictionary.keys():
                        if oi != ANY: # object is given
                            if oi in subjectDictionary[p]:
                                ss, pp, oo = self.intToIdentifier((si, p, oi))
                                yield (ss, pp, oo), (c for c in self.contexts((ss, pp, oo)))
                            else: # given object not found
                                pass
                        else: # object unbound
                            for o in subjectDictionary[p].keys():
                                ss, pp, oo = self.intToIdentifier((si, p, o))
                                yield (ss, pp, oo), (c for c in self.contexts((ss, pp, oo)))
            else: # given subject not found
                pass
        elif pi != ANY: # predicate is given, subject unbound
            if pi in pos:
                predicateDictionary = pos[pi]
                if oi != ANY: # predicate+object is given, subject unbound
                    if oi in predicateDictionary:
                        for s in predicateDictionary[oi].keys():
                            ss, pp, oo = self.intToIdentifier((s, pi, oi))
                            yield (ss, pp, oo), (c for c in self.contexts((ss, pp, oo)))
                    else: # given object not found
                        pass
                else: # predicate is given, object+subject unbound
                    for o in predicateDictionary.keys():
                        for s in predicateDictionary[o].keys():
                            ss, pp, oo = self.intToIdentifier((s, pi, o))
                            yield (ss, pp, oo), (c for c in self.contexts((ss, pp, oo)))
        elif oi != ANY: # object is given, subject+predicate unbound
            if oi in osp:
                objectDictionary = osp[oi]
                for s in objectDictionary.keys():
                    for p in objectDictionary[s].keys():
                        ss, pp, oo = self.intToIdentifier((s, p, oi))
                        yield (ss, pp, oo), (c for c in self.contexts((ss, pp, oo)))
        else: # subject+predicate+object unbound
            for s in spo.keys():
                subjectDictionary = spo[s]
                for p in subjectDictionary.keys():
                    for o in subjectDictionary[p].keys():
                        ss, pp, oo = self.intToIdentifier((s, p, o))
                        yield (ss, pp, oo), (c for c in self.contexts((ss, pp, oo)))

    def __len__(self, context=None):

        if context is not None:
            if context == self:
                context = None

        # TODO: for eff. implementation
        count = 0
        for triple, cg in self.triples((ANY, ANY, ANY), context):
            count += 1
        return count

    def contexts(self, triple=None):
        if triple:
            si, pi, oi = self.identifierToInt(triple)
            for ci in self.spo[si][pi][oi]:
                yield self.forward[ci]
        else:
            for ci in self.cspo.keys():
                yield self.forward[ci]


def randid(randint=random.randint, choice=random.choice, signs=(-1,1)):
    return choice(signs)*randint(1,2000000000)

del random

# From: http://www.w3.org/TR/REC-xml#NT-CombiningChar
#
# * Name start characters must have one of the categories Ll, Lu, Lo,
#   Lt, Nl.
#
# * Name characters other than Name-start characters must have one of
#   the categories Mc, Me, Mn, Lm, or Nd.
#
# * Characters in the compatibility area (i.e. with character code
#   greater than #xF900 and less than #xFFFE) are not allowed in XML
#   names.
#
# * Characters which have a font or compatibility decomposition
#   (i.e. those with a "compatibility formatting tag" in field 5 of the
#   database -- marked by field 5 beginning with a "<") are not allowed.
#
# * The following characters are treated as name-start characters rather
#   than name characters, because the property file classifies them as
#   Alphabetic: [#x02BB-#x02C1], #x0559, #x06E5, #x06E6.
#
# * Characters #x20DD-#x20E0 are excluded (in accordance with Unicode
#   2.0, section 5.14).
#
# * Character #x00B7 is classified as an extender, because the property
#   list so identifies it.
#
# * Character #x0387 is added as a name character, because #x00B7 is its
#   canonical equivalent.
#
# * Characters ':' and '_' are allowed as name-start characters.
#
# * Characters '-' and '.' are allowed as name characters.


NAME_START_CATEGORIES = ["Ll", "Lu", "Lo", "Lt", "Nl"]
NAME_CATEGORIES = NAME_START_CATEGORIES + ["Mc", "Me", "Mn", "Lm", "Nd"]
ALLOWED_NAME_CHARS = ["\u00B7", "\u0387", "-", ".", "_"]

# http://www.w3.org/TR/REC-xml-names/#NT-NCName
#  [4] NCName ::= (Letter | '_') (NCNameChar)* /* An XML Name, minus
#      the ":" */
#  [5] NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar
#      | Extender

def is_ncname(name):
    first = name[0]
    if first=="_" or unicodedata.category(first) in NAME_START_CATEGORIES:
        for i in range(1, len(name)):
            c = name[i]
            if not unicodedata.category(c) in NAME_CATEGORIES:
                if c in ALLOWED_NAME_CHARS:
                    continue
                return 0
            #if in compatibility area
            #if unicodedata.decomposition(c)!='':
            #    return 0

        return 1
    else:
        return 0

def split_uri(uri):
    uri = str(uri)
    if uri.startswith(XMLNS):
        return (XMLNS, uri.split(XMLNS)[1])
    length = len(uri)
    for i in range(0, length):
        c = uri[-i-1]
        if not unicodedata.category(c) in NAME_CATEGORIES:
            if c in ALLOWED_NAME_CHARS:
                continue
            for j in range(-1-i, length):
                if unicodedata.category(uri[j]) in NAME_START_CATEGORIES or uri[j]=="_":
                    ns = uri[:j]
                    if not ns:
                        break
                    ln = uri[j:]
                    return (ns, ln)
            break
    raise Exception("Can't split '%s'" % uri)
